﻿namespace How_toEval;

using static AbstractSyntaxTree;

public class Evaler
{
    public Evaler(VarScanner scanner, AbstractSyntaxTree abstractSyntaxTree)
    {
        AST = abstractSyntaxTree;
        GlobalMemory = new Memory(new Dictionary<string, object>(), Builtins.BuiltinMemory);
        StackFrames = new LinkedList<Memory>();
        StackFrames.AddFirst(GlobalMemory);
        Scanner = scanner;
        VarsSkip = Scanner.VarsSkip(abstractSyntaxTree);
    }

    Memory GlobalMemory { get; set; }

    LinkedList<Memory> StackFrames { get; set; }

    Dictionary<AbstractSyntaxTree, int>? VarsSkip { get; set; }

    Memory CurrentMemory { get => StackFrames.First(); }

    AbstractSyntaxTree AST { get; set; }

    VarScanner Scanner { get; }

    private object Eval(AbstractSyntaxTree abstractSyntaxTree)
    {
        return abstractSyntaxTree switch
        {
            BinaryOperateNode binaryOperaterNode => EvalBinaryOperator(binaryOperaterNode),
            Float @float => EvalFloat(@float),
            Integer integer => EvalInt(integer),
            Group group => EvalGroup(group),
            Define define => EvalDefine(define),
            Var var => EvalVar(var),
            Function function => EvalFunction(function),
            Call call => EvalCall(call),
            AnnotatedExpression anno => Eval(anno.Expression),
            _ => throw new Exception("Evaler can't handle such node"),
        };
    }

    public object Run()
    {
        return Eval(AST);
    }

    private object EvalCall(Call call)
    {
        var vs = call.Arguments.Select(v => Eval(v)).ToList();
        switch (Eval(call.Called))
        {
            case Closure closure:
                StackFrames.AddFirst(new Memory(new Dictionary<string, object>(), closure.SaveMemory));
                // 注意：从这里开始上下文已经变化
                for (int i = 0; i < call.Arguments.Count; i++)
                {
                    CurrentMemory.Set(closure.Params[i].Item1, vs[i]);
                }
                var res = Eval(closure.FunctionBody);
                StackFrames.RemoveFirst();
                return res;
            case Lazy<IBuiltin> builtin:
                if (call.Arguments.Count != builtin.Value.ParametersNumber)
                {
                    throw new Exception($"Builtin function need {builtin.Value.ParametersNumber} parameters, but {call.Arguments.Count} was given");
                }
                return builtin.Value.BuiltinHandler(vs.ToArray());
            case var o:
                throw new Exception($"Called object {o} is not callable");
        }

    }

    private Closure EvalFunction(Function function)
    {
        Closure closure = new Closure(CurrentMemory, function.Body, function.Params);
        return closure;
    }

    private object EvalVar(Var var)
    {
        Memory? cmem = CurrentMemory;
        for (var i = 0; i < VarsSkip![var]; i++)
        {
            cmem = cmem!.EnclosingMemory;
        }
        return cmem!.Get(var.Name);
    }

    object EvalGroup(Group group)
    {
        object result = 0;
        foreach (AbstractSyntaxTree tree in group.Leaves)
        {
            result = Eval(tree);
        }
        return result;
    }

    double EvalFloat(Float @float)
    {
        return @float.Value;
    }

    double EvalInt(Integer @int)
    {
        return @int.Value;
    }

    double EvalBinaryOperator(BinaryOperateNode binaryOperaterNode)
    {
        if (binaryOperaterNode.Operator == "+")
        {
            return (double)Eval(binaryOperaterNode.Left) + (double)Eval(binaryOperaterNode.Right);
        }
        else if (binaryOperaterNode.Operator == "-")
        {
            return (double)(Eval(binaryOperaterNode.Left)) - (double)(Eval(binaryOperaterNode.Right));
        }
        else if (binaryOperaterNode.Operator == "*")
        {
            return (double)Eval(binaryOperaterNode.Left) * (double)Eval(binaryOperaterNode.Right);
        }
        else if (binaryOperaterNode.Operator == "/")
        {
            return (double)Eval(binaryOperaterNode.Left) / (double)Eval(binaryOperaterNode.Right);
        }
        else throw new Exception("No such operator");
    }

    object EvalDefine(Define define)
    {
        object res = Eval(define.Value);
        CurrentMemory.Set(define.Name, res);
        return res;
    }
}
